#include "ogl/model/Mesh.h"
#include "ogl/model/Shader.h"
#include "ogl/model/BoneNode.h"
#include "ogl/phong/Effect.h"
#include "ogl/math/SkinnedMeshData.h"
#include "Mesh.h"

using namespace Ogl::Model;

Ogl::Model::Mesh::Mesh(aiMesh *mesh)
{
    m_AABB = AABB(mesh->mAABB);
    m_MaterialIndex = mesh->mMaterialIndex;
    Load(mesh->mName, m_Name);

    for (int boneId = 0; boneId < mesh->mNumBones; boneId++)
    {

        auto newBone = MakeRef<Bone>(mesh->mBones[boneId]);
        newBone->m_Index = boneId;
        m_Bones[newBone->m_Name] = newBone;

    }


    CreateRenderMesh(mesh);
}

bool Ogl::Model::Mesh::CreateRenderMesh(aiMesh* mesh)
{

    Ogl::Math::SkinnedMeshData m_SkinnedMeshData;

    Ogl::Math::MeshData m_MeshData;

    if (mesh->mNumVertices > 0)
    {
        std::vector<unsigned int>& m_Indices = m_MeshData.indices32;

        for (int i = 0; i < mesh->mNumFaces; i++)
        {
            auto face = mesh->mFaces[i];
            for (int j = 0; j < face.mNumIndices; j++)
            {
                m_Indices.push_back(face.mIndices[j]);
            }
        }

        //m_Mesh->RegisterIndexBuffer<unsigned int>(m_Indices);
        m_MeshData.ResizeVertices(mesh->mNumVertices);
        memcpy(&m_MeshData.vertices[0], mesh->mVertices, sizeof(glm::vec3) * mesh->mNumVertices);
        memcpy(&m_MeshData.normals[0], mesh->mNormals, sizeof(glm::vec3) * mesh->mNumVertices);
       
        for (int i = 0; i < mesh->mNumVertices; i++)
        {
            glm::vec2& texCoord = m_MeshData.texcoords[i];
            texCoord.x = mesh->mTextureCoords[0][i].x;
            texCoord.y = mesh->mTextureCoords[0][i].y;
        }
        if (mesh->HasTangentsAndBitangents())
        {
            memcpy(&m_MeshData.tangents[0], mesh->mTangents, sizeof(glm::vec3) * mesh->mNumVertices);
            memcpy(&m_MeshData.bigTangents[0], mesh->mBitangents, sizeof(glm::vec3) * mesh->mNumVertices);
        }
        m_Mesh = std::make_shared<Ogl::Gut::Mesh>(m_MeshData);

    }
    if (HasBones())
    {
        // std::cout << "has bones" << std::endl;

        m_SkinnedMeshData.Resize(mesh->mNumVertices);
          
        for (auto it : m_Bones)
        {
            std::string name = it.first;
            auto bone = it.second;
        
            int boneIndex = bone->m_Index;

            for (const auto& weight : bone->m_Weights)
            {
                int vertexId = weight.m_VertexId;
                float weightValue = weight.m_Weight;
                m_SkinnedMeshData.AddBoneWeight(vertexId, boneIndex, weightValue);
            }
        }

        m_Mesh->Bind();
        m_Mesh->LoadSkinnedMeshData(m_SkinnedMeshData);

    }

    m_Mesh->UnBind();

    {
        Ogl::Gut::Image desc(800, 600, 4, false, false);
        //desc.m_NumTextures= 1;
        m_MeshRT = std::make_shared<Ogl::Gut::RenderTarget>(desc, 1);
        m_SkinnedMeshRT = std::make_shared<Ogl::Gut::RenderTarget>(desc, 1);
    }

    return true;
}


bool Ogl::Model::Mesh::CreateSnapshot(const Ogl::Model::Node* rootNode)
{
    Ref<Ogl::Phong::Effect> effect = MakeRef<Ogl::Phong::Effect>();
    effect->m_RasterizerDesc.cullFace = false;

    Ogl::Phong::LightEffect lightEffect;
    lightEffect.numDirlights = 1;

    Ogl::Phong::Material material;

    int width = 800;
    int height = 600;
    float aspect = (float)width / (float)height;
    Ogl::Math::Camera camera;
    Ogl::Math::Transform transform;


    float distanceAxis = 3.0f;

    float halfExtendsLength = glm::length(m_AABB.m_HalfExtends);

    camera.SetPosition(m_AABB.m_Center + halfExtendsLength  * distanceAxis* glm::vec3(0.f, 0.0f, 1.f));
    camera.LookAt(m_AABB.m_Center);


    m_MeshRT->Begin();

    glClearColor(.2f, 0.2f,0.2f, 0.2f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    effect->Begin();
    effect->SetCamera(camera);
    effect->SetLightEffect(lightEffect);
    effect->RenderMesh(m_Mesh.get(), transform, material);
    effect->End();

    m_MeshRT->End();

    if (HasBones())
    {
        std::shared_ptr<Ogl::Model::BoneNode> boneNode = std::make_shared<Ogl::Model::BoneNode>(rootNode, this->m_Bones);
        Ogl::Gut::Effect3d::BoneData boneData;

        m_SkinnedMeshRT->Begin();

        glClearColor(.2f, 0.2f, 0.2f, 0.2f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        effect->Begin();
        effect->SetCamera(camera);
        effect->SetLightEffect(lightEffect);

        boneNode->ProcessNode(glm::mat4(1.0f));
        boneNode->UpdateBuffer(boneData);

        effect->RenderSkinnedMesh(m_Mesh.get(), transform, boneData, material);
        effect->End();

        m_SkinnedMeshRT->End();

    }


    return true;
}

bool Ogl::Model::Mesh::HasBones() const
{
    return this->m_Bones.size() > 0;

}

